local M = {} -- change this to true for: -- ignore mission precondition -- print how many attakers there will be -- make attackers die as soon they spawn for local _DEBUG = false function on_game_start() RegisterScriptCallback("save_state",save_state) RegisterScriptCallback("load_state",load_state) RegisterScriptCallback("actor_on_first_update", actor_on_first_update) end local news = dynamic_news_helper.send_tip function mr(n,inc_0) if inc_0 then return math.random(n+1)-1 else return math.random(n) end end function save_state(m_data) m_data.pump_station_def_csky = M end function load_state(m_data) M = m_data.pump_station_def_csky or {} end function nkeys(t) local i = 0 for k in pairs(t) do i = i +1 end return i end function rkeys(t) local i = {} for k in pairs(t) do i[#i+1] = k end return i[math.random(#i)] end function actor_on_first_update() if M and M.cleanup_on_new_map and not (level.name() == 'k00_marsh') then cleanup() end end function give_before_attack_talk() M.before_attack_talk = true end function is_commader_replaced() return M.commader_replaced end function dist_from_trigger() local obj = level.object_by_id(M.trigger_id) return db.actor:position():distance_to_sqr(obj:position()) end function get_news_data(id) if id then local se_obj = alife():object(id) if se_obj and se_obj:alive() then return game.translate_string(se_obj:character_name()), se_obj:character_icon() end else local sim = alife() local se_obj = sim:object(M.commander_id) if se_obj and se_obj:alive() then return game.translate_string(se_obj:character_name()), se_obj:character_icon(), "st_psd_end_1" else for id in pairs(M.guards) do se_obj = sim:object(id) if se_obj and se_obj:alive() then M.commader_replaced = true M.commander_id = id M.guards[id] = nil logic_enforcer.assign(id,'scripts\\tasks\\pump_station_defense_task.ltx','logic','beh@guard_commander') return game.translate_string(se_obj:character_name()), se_obj:character_icon(), "st_psd_end_2" end end end end end function intro_message() local name, icon = get_news_data(M.commander_id) if name and icon then news(game.translate_string("st_psd_intro"),name,nil,nil, icon, nil, 'npc') end end function start_message() local name, icon = get_news_data(M.commander_id) if name and icon then news(game.translate_string("st_psd_start"),name,nil,nil, icon, nil, 'npc') end end function loot_message() local name, icon, msg = get_news_data() if name and icon and msg then news(game.translate_string(msg),name,nil,nil, icon, nil, 'npc') end end function xr_conditions.no_guns(a,b) local found = false local function itr(x,itm) if string.find(itm:section(),"wpn_") then found = true end end b:iterate_inventory(itr,b) return not found end function xr_conditions.pump_station_defense_task_precondition() -- skip checks if debugging if _DEBUG then return true end -- check nobody is/is going there -- nevermind -- local sm = SIMBOARD.smarts_by_names['mar_smart_terrain_5_8'] -- local id = sm.id -- local sim = alife() -- for sid in pairs(SIMBOARD.squads) do -- local squad = sim:object(sid) -- if squad and squad.assigned_target_id == id then -- -- printf('squad %s has assigned pump station', squad:name()) -- return false -- end -- end -- available only after 14 days return math.floor((game.get_game_time():diffSec(level.get_start_time())) / (60*60*24)) >= 14 end function xr_conditions.pump_station_defense_task_commander_inv_condition() if not M.stage then return false end -- mortal when task failed/over local a = M.stage and M.stage < 3 local b = not aggro() return (a and b) -- attack hasnt started AND not enemy of player end function xr_conditions.pump_station_defense_task_need_intro_talk() return M.stage and M.stage < 3 and not M.before_attack_talk end function xr_conditions.pump_station_defense_task_need_intro_talk_2() return M.stage and M.stage < 3 and M.before_attack_talk end function xr_conditions.pump_station_defense_task_need_end_talk() return M.stage and M.stage > 3 end function xr_conditions.pump_station_defense_task_commander_replaced() -- printf('pump_station_defense_task_commander_replaced: %s, stage %s',M.commader_replaced and M.commader_replaced, M.stage and M.stage) return M.stage and M.stage > 3 and M.commader_replaced end function xr_effects.pump_station_defense_reward() for i,v in ipairs(M.reward) do xr_effects.reward_item(0,0,{v}) end end function xr_effects.pump_station_defense_task_cleanup(a,b,c) for id in pairs(M.marked) do level.map_remove_object_spot(id, "secondary_task_location") end if c and c[1] and c[1]=='fail' then if level.name() == 'k00_marsh' then M.cleanup_on_new_map = true else cleanup() end else cleanup() end end function cleanup() local s = alife() if M.commander_id and s:object(M.commander_id) then safe_release_manager.release({id = M.commander_id}) end if M.stash_id then alife_release_id(M.stash_id) end for id in pairs(M.to_release_at_end) do if s:object(id) then safe_release_manager.release({id = id}) end end for id in pairs(M.guards) do if s:object(id) then safe_release_manager.release({id = id}) end end M = {} end function escape_message() actor_menu.set_msg(1, "st_psd_escape",10) end function warning_message() actor_menu.set_msg(1, "st_psd_warning",10) end function aggro() if M.commander_id then local comm = level.object_by_id(M.commander_id) local be = comm and comm:best_enemy() if be and be:id() == 0 then return true end if db.storage[M.commander_id] and db.storage[M.commander_id].enemy_id and db.storage[M.commander_id].enemy_id == 0 then return true end end if not M.guards then return end for id in pairs(M.guards) do local grd = level.object_by_id(id) local be = grd and grd:best_enemy() if be and be:id() == 0 then return true end end end local f_stage ={} local tgt_stage = {} -----------------------------------------// stage 0 local _GUNS_FOR_GUARDS = { {"wpn_ak","ammo_7.62x39_fmj"}, {"wpn_ak74","ammo_5.45x39_fmj"}, {"wpn_akm","ammo_7.62x39_fmj"}, {"wpn_aks74","ammo_5.45x39_fmj"}, {"wpn_ak74u","ammo_5.45x39_fmj"}, -- {'wpn_lr300_custom','ammo_5.56x45_fmj'}, } function spawn_and_set(kill,sec,x,y,z,lvid,gvid,guard_profile,give_guns) local se = alife_create(sec,vector():set(x,y,z),lvid,gvid) if kill then se.angle = vector(0,mr(1,true),0) M.to_release_at_end[se.id] = true M.strip[se.id] = true logic_enforcer.assign(se.id,'scripts\\tasks\\pump_station_defense_task.ltx','logic','beh@morituri') else logic_enforcer.assign(se.id,'scripts\\tasks\\pump_station_defense_task.ltx','logic','beh@guard_'..guard_profile) end if give_guns then local g = _GUNS_FOR_GUARDS[mr(#_GUNS_FOR_GUARDS)] alife_create_item(g[1], se) alife_create_item(g[2], se) alife_create_item(g[3], se) end return se.id end function clean_corpses() if M.strip then for id in pairs(M.strip) do local obj = level.object_by_id(id) if obj and not obj:alive() then se_save_var(id,nil,"death_dropped",true) local items = {} local function itr(x,itm) items[itm:id()] = true end obj:iterate_inventory(itr,obj) for xd in pairs(items) do alife_release_id(xd) end end end end end f_stage[0] = function(tsk,task_id) if not M.init then -- db.actor:give_info_portion('pump_station_defense_task_smart_reserved') local sim = alife() M.init = true M.to_release_at_end = {} M.strip = {} M.marked = {} M.spawn_index = 1 local days = math.floor((game.get_game_time():diffSec(level.get_start_time())) / (60*60*24)) M.attackers_to_spawn = math.min(20 + days, 50) if _DEBUG then printf('attackers: %s', M.attackers_to_spawn) end M.attackers_at_once = 10 M.attackers = {} M.guards = {} M.attackers_sections = { "sim_default_bandit_0", "sim_default_bandit_0", "sim_default_bandit_0", "sim_default_bandit_1", "sim_default_bandit_1", "sim_default_bandit_1", "sim_default_bandit_2", "sim_default_bandit_2", "sim_default_bandit_2", "sim_default_bandit_3", } M.spawn_points = { {-13.780313491821,0.74562031030655,-40.790405273438,136337,20 }, {-0.22422948479652,0.35323888063431,-48.327178955078,142310,20}, {54.677856445313,0.60878366231918,34.824234008789,171341,183 }, {56.228652954102,0.53593975305557,38.849433898926,172574,183 }, {-49.463119506836,0.79843503236771,32.798267364502,116796,335 }, {-36.343528747559,0.41626954078674,44.38663482666,124543,335 }, } local sm = SIMBOARD.smarts_by_names['mar_smart_terrain_5_8'] M.smart_id = sm.id p = sm.position M.smart_pos = {x=p.x,y=p.y,z=p.z} local pool = { "af_dummy_battery_lead_box", "af_dummy_glassbeads_lead_box", "af_electra_moonlight_lead_box", "af_night_star_lead_box", "af_soul_lead_box", } M.reward = { pool[math.random(#pool)], } local days = math.min(math.floor((game.get_game_time():diffSec(level.get_start_time())) / (60*60*24)), 60) while days > 0 do table.insert(M.reward,pool[math.random(#pool)]) days = days - 20 end -- dead sniper M.trigger_id = spawn_and_set(true,'sim_default_csky_3',-5.5388970375061,11.186992645264,20.451383590698,139764,16) local trig = alife():object(M.trigger_id) local p = trig.position M.trigger_pos = {x=p.x,y=p.y,z=p.z} -- commander M.commander_id = spawn_and_set(false,'sim_default_csky_4',-16.133546829224,4.8830790519714,13.973717689514,135494,16, "commander",true) -- (soon) dead bandits spawn_and_set(true ,'sim_default_bandit_'..mr(2,true),1.9396654367447,1.0203063488007,-13.762584686279,143385,17) spawn_and_set(true ,'sim_default_bandit_'..mr(2,true),-0.54266440868378,0.73321121931076,-18.496885299683,141978,17) spawn_and_set(true ,'sim_default_bandit_'..mr(2,true),-4.9029340744019,0.50968080759048,-20.897457122803,140028,19) spawn_and_set(true ,'sim_default_bandit_'..mr(2,true),-6.0652461051941,0.70677882432938,-27.802486419678,139435,19) spawn_and_set(true ,'sim_default_bandit_'..mr(2,true),-7.4985198974609,0.43960136175156,-38.498107910156,138848,20) spawn_and_set(true ,'sim_default_bandit_'..mr(2,true),21.00622177124,0.6645775437355,14.144376754761,152905,36) spawn_and_set(true ,'sim_default_bandit_'..mr(2,true),27.007202148438,0.76654106378555,13.693680763245,156446,35) spawn_and_set(true ,'sim_default_bandit_'..mr(2,true),-0.24199193716049,4.3560590744019,32.728443145752,142386,65) -- guards M.guards[spawn_and_set(false,'sim_default_csky_'..mr(2,true),5.0465126037598,3.2883059978485,18.298782348633,144660,36, 1 , true)] = true M.guards[spawn_and_set(false,'sim_default_csky_'..mr(2,true),4.9958162307739,3.2920978069305,12.101389884949,144651,36, 2 , true)] = true M.guards[spawn_and_set(false,'sim_default_csky_'..mr(2,true),3.1273865699768,2.1526610851288,-3.6081943511963,143737,18, 3 , true)] = true M.guards[spawn_and_set(false,'sim_default_csky_'..mr(2,true),-0.53463208675385,2.147492647171,-6.2311568260193,141979,18,4 , true)] = true M.guards[spawn_and_set(false,'sim_default_csky_'..mr(2,true),-5.9249491691589,3.3302540779114,6.0563468933105,139738,16, 5 , true)] = true M.guards[spawn_and_set(false,'sim_default_csky_'..mr(2,true),-8.3903884887695,3.3009197711945,21.665546417236,138572,36, 6 , true)] = true M.guards[spawn_and_set(false,'sim_default_csky_'..mr(2,true),-19.692607879639,4.9291095733643,16.863489151001,134017,16, 8 , true)] = true return end if db.actor:position():distance_to_sqr(vector():set(M.smart_pos.x,M.smart_pos.y,M.smart_pos.z)) < 3000 then local se = alife_object(M.trigger_id) if se then clean_corpses() --alife_create_item('ammo_7.62x54_7h1', se) alife_create_item('ammo_7.62x54_7h1', se, { ammo = 3 }) local mosin = alife_create('wpn_mosin',se.position,se.m_level_vertex_id,se.m_game_vertex_id,se.id,false) local data = utils_stpk.get_weapon_data(mosin) data.addon_flags = 1 data.condition = 0.5 utils_stpk.set_weapon_data(data,mosin) alife():register(mosin) intro_message() return 1 end end end tgt_stage[0] = function(tsk,task_id) return M and M.smart_id end -----------------------------------------// stage 1 f_stage[1] = function(tsk,task_id) if M.before_attack_talk then return 2 end end tgt_stage[1] = function(tsk,task_id) return M.commander_id end -----------------------------------------// stage 2 f_stage[2] = function(tsk,task_id) if db.actor:position():distance_to_sqr(vector():set(M.trigger_pos.x,M.trigger_pos.y,M.trigger_pos.z)) < 25 then start_message() return 3 end end tgt_stage[2] = function(tsk,task_id) return M.trigger_id end -----------------------------------------// stage 3 f_stage[3] = function(tsk,task_id) local sim = alife() if M.attackers_to_spawn > 0 and nkeys(M.attackers) < M.attackers_at_once then M.attackers_to_spawn = M.attackers_to_spawn - 1 -- printf('left: %s',M.attackers_to_spawn) local sp = M.spawn_points[M.spawn_index] M.spawn_index = M.spawn_index == #M.spawn_points and 1 or M.spawn_index + 1 pos = vector():set(sp[1], sp[2], sp[3]) local sec = M.attackers_sections[math.random(#M.attackers_sections)] local se_attacker = alife_create(sec,pos,sp[4],sp[5]) M.attackers[se_attacker.id] = true M.to_release_at_end[se_attacker.id] = true -- make attackers die as soon they are spawned when debugging logic_enforcer.assign(se_attacker.id,'scripts\\tasks\\pump_station_defense_task.ltx','logic',_DEBUG and 'beh@morituri' or 'beh@attacker') M.marked[se_attacker.id] = true level.map_add_object_spot_ser(se_attacker.id, "secondary_task_location", 'Bandit') return end for id in pairs(M.attackers) do local sea = sim:object(id) if not (sea and sea:alive()) then level.map_remove_object_spot(id, "secondary_task_location") M.marked[id] = nil M.attackers[id] = nil end end local dist = vector():set(M.smart_pos.x, M.smart_pos.y, M.smart_pos.z):distance_to_sqr(db.actor:position()) if dist > 3000 then escape_message() return "fail" end if dist > 2000 and not M.warning_done then warning_message() M.warning_done = true end if M.attackers_to_spawn == 0 and nkeys(M.attackers) == 0 then loot_message() return 6 end end -----------------------------------------// stage 6 tgt_stage[6] = function(tsk,task_id) return tsk.task_giver_id end task_status_functor.gd_task_status_functor = function(tsk,task_id) if not (db.actor and tsk) then return end local stage = tsk.stage M.stage = stage if not (level.name() == 'k00_marsh') then return 'fail' end if aggro() then return 'fail' end local ret = f_stage[stage] and f_stage[stage](tsk,task_id) or stage if tonumber(ret) then tsk.stage = ret else return ret end end task_functor.gd_task_target_functor = function(task_id,field,p,tsk) if not (db.actor and tsk) then return nil end local stage = tsk.stage if tgt_stage[stage] then return tgt_stage[stage](tsk,task_id) end return nil end local _STAGE_DESC = { [0] = "st_psd_stage_0", [1] = "st_psd_stage_1", [2] = "st_psd_stage_2", [3] = "st_psd_stage_3", [5] = "st_psd_stage_5", [6] = "st_psd_stage_6", } task_functor.gd_task_descr_functor = function(task_id,field,p,tsk) if not (db.actor and tsk) then return nil end local stage = tsk.stage return _STAGE_DESC[stage] end